#include <iostream>
#include <stdlib.h>
#include <string.h>
#include "support.h"
#include "ResultDatabase.h"
#include "OptionParser.h"
#include "Timer.h"
#include "kesour.h"

using namespace std;


void addBenchmarkSpecOptions(OptionParser &op) {
   ;
}

void RunBenchmark(cl::Device& devcpp,
                  cl::Context& ctxcpp,
                  cl::CommandQueue& queuecpp,
                  ResultDatabase &resultDB,
                  OptionParser &op)
{
    // convert from C++ bindings to C bindings
    // TODO propagate use of C++ bindings
    cl_device_id id = devcpp();
    cl_context ctx = ctxcpp();
    cl_command_queue queue = queuecpp();

    int n_passes = op.getOptionInt("passes");
    int err;
    
    for (int pass=0 ; pass<n_passes ; ++pass)
    {
        //
        // Short kernel
        //

        // Create the program
        int TH_short_create = Timer::Start();
        cl_program short_prog = clCreateProgramWithSource(ctx, 1,
                                                          &short_source, NULL,
                                                          &err);
        double len_short_create = Timer::Stop(TH_short_create, "TH_short_create");
        CL_CHECK_ERROR(err);
    
        int TH_short_build = Timer::Start();
        // Compile the program
        err = clBuildProgram (short_prog, 0, NULL, NULL, NULL, NULL);
        double len_short_build = Timer::Stop(TH_short_build, "TH_short_build");
        CL_CHECK_ERROR(err);

        // Extract out kernel 
        int TH_short_extract = Timer::Start();
        cl_kernel short_kernel = clCreateKernel(short_prog, "Triad", &err);
        double len_short_extract = Timer::Stop(TH_short_extract, "TH_short_extract");
        CL_CHECK_ERROR(err);

        // Cleanup
        int TH_short_cleanup = Timer::Start();
        err = clReleaseKernel(short_kernel);
        CL_CHECK_ERROR(err);
        err = clReleaseProgram(short_prog);
        CL_CHECK_ERROR(err);
        double len_short_cleanup = Timer::Stop(TH_short_cleanup, "TH_short_cleanup");

/*
	//
	// short to long

	double total_s2l_create = 0.0;
	double total_s2l_build = 0.0;
	double total_s2l_extract = 0.0;
	double total_s2l_cleanup = 0.0;

	for (int s2l=0; s2l<26; s2l++) 
	{	

        	int TH_s2l_create = Timer::Start();
        	cl_program short_prog = clCreateProgramWithSource(ctx, 1,
                                                          &short_source, NULL,
                                                          &err);
        	double len_s2l_create = Timer::Stop(TH_s2l_create, "TH_s2l_create");
        	CL_CHECK_ERROR(err);
		total_s2l_create += len_s2l_create;
    
        	int TH_s2l_build = Timer::Start();
        	// Compile the program
        	err = clBuildProgram (short_prog, 0, NULL, NULL, NULL, NULL);
        	double len_s2l_build = Timer::Stop(TH_s2l_build, "TH_s2l_build");
        	CL_CHECK_ERROR(err);
		total_s2l_build += len_s2l_build;

       		 // Extract out kernel 
        	int TH_s2l_extract = Timer::Start();
        	cl_kernel short_kernel = clCreateKernel(short_prog, "Triad", &err);
        	double len_s2l_extract = Timer::Stop(TH_s2l_extract, "TH_s2l_extract");
        	CL_CHECK_ERROR(err);
		total_s2l_extract += len_s2l_extract;

        	// Cleanup
        	int TH_s2l_cleanup = Timer::Start();
        	err = clReleaseKernel(short_kernel);
        	CL_CHECK_ERROR(err);
        	err = clReleaseProgram(short_prog);
        	CL_CHECK_ERROR(err);
        	double len_s2l_cleanup = Timer::Stop(TH_s2l_cleanup, "TH_s2l_cleanup");
		total_s2l_cleanup += len_s2l_cleanup;
	}
*/
        //
        // Long kernel
        //

        // Create the program
        int TH_long_create = Timer::Start();
        cl_program long_prog = clCreateProgramWithSource(ctx, 1,
                                                         &long_source, NULL,
                                                         &err);
        double len_long_create = Timer::Stop(TH_long_create, "TH_long_create");
        CL_CHECK_ERROR(err);
    
        // Compile the program
        int TH_long_build = Timer::Start();
        err = clBuildProgram (long_prog, 0, NULL, NULL, NULL, NULL);
        double len_long_build = Timer::Stop(TH_long_build, "TH_long_build");
        CL_CHECK_ERROR(err);

        // Extract out kernel 
        int TH_long_extract = Timer::Start();
        cl_kernel long_kernel = clCreateKernel(long_prog, "scan", &err);
        double len_long_extract = Timer::Stop(TH_long_extract, "TH_long_extract");
        CL_CHECK_ERROR(err);

        // Cleanup
        int TH_long_cleanup = Timer::Start();
        err = clReleaseKernel(long_kernel);
        CL_CHECK_ERROR(err);
        err = clReleaseProgram(long_prog);
        CL_CHECK_ERROR(err);
        double len_long_cleanup = Timer::Stop(TH_long_cleanup, "TH_long_cleanup");

	//  matrix_kernels super long and multi-kernels
        // Create the program
        int TH_mk_create = Timer::Start();
        cl_program mk_prog = clCreateProgramWithSource(ctx, 1,
                                                         (const char **)&matrix_kernels, NULL,
                                                         &err);
        double len_mk_create = Timer::Stop(TH_mk_create, "TH_mk_create");
        CL_CHECK_ERROR(err);

        // Compile the program
        int TH_mk_build = Timer::Start();
        err = clBuildProgram (mk_prog, 0, NULL, NULL, NULL, NULL);
        double len_mk_build = Timer::Stop(TH_mk_build, "TH_mk_build");
        CL_CHECK_ERROR(err);

        // Extract out kernel 
        int TH_mk_extract_1 = Timer::Start();
        cl_kernel mk_kernel_1 = clCreateKernel(mk_prog, "prod_TT", &err);
        double len_mk_extract_1 = Timer::Stop(TH_mk_extract_1, "TH_mk_extract_1");
        CL_CHECK_ERROR(err);

        int TH_mk_extract_2 = Timer::Start();
        cl_kernel mk_kernel_2 = clCreateKernel(mk_prog, "inplace_sub", &err);
        double len_mk_extract_2 = Timer::Stop(TH_mk_extract_2, "TH_mk_extract_2");
        CL_CHECK_ERROR(err);

        int TH_mk_extract_3 = Timer::Start();
        cl_kernel mk_kernel_3 = clCreateKernel(mk_prog, "lu_factorize", &err);
        double len_mk_extract_3 = Timer::Stop(TH_mk_extract_3, "TH_mk_extract_3");
        CL_CHECK_ERROR(err);

        int TH_mk_extract_4 = Timer::Start();
        cl_kernel mk_kernel_4 = clCreateKernel(mk_prog, "unit_upper_triangular_substitute_inplace", &err);
        double len_mk_extract_4 = Timer::Stop(TH_mk_extract_4, "TH_mk_extract_4");
        CL_CHECK_ERROR(err);

        int TH_mk_extract_5 = Timer::Start();
        cl_kernel mk_kernel_5 = clCreateKernel(mk_prog, "upper_triangular_substitute_inplace", &err);
        double len_mk_extract_5 = Timer::Stop(TH_mk_extract_5, "TH_mk_extract_5");
        CL_CHECK_ERROR(err);

        // Cleanup
        int TH_mk_cleanup = Timer::Start();
        err = clReleaseKernel(mk_kernel_1);
        CL_CHECK_ERROR(err);
        err = clReleaseProgram(mk_prog);
        CL_CHECK_ERROR(err);
        double len_mk_cleanup = Timer::Stop(TH_mk_cleanup, "TH_mk_cleanup");


        //
        // mk_kernel_2 about 318 lines
        //

        // Create the program
        int TH_mk2_create = Timer::Start();
        cl_program mk2_prog = clCreateProgramWithSource(ctx, 1,
                                                         &matrix_kernels_2, NULL,
                                                         &err);
        double len_mk2_create = Timer::Stop(TH_mk2_create, "TH_mk2_create");
        CL_CHECK_ERROR(err);
    
        // Compile the program
        int TH_mk2_build = Timer::Start();
        err = clBuildProgram (mk2_prog, 0, NULL, NULL, NULL, NULL);
        double len_mk2_build = Timer::Stop(TH_mk2_build, "TH_mk2_build");
        CL_CHECK_ERROR(err);

        // Extract out kernel 
        int TH_mk2_extract = Timer::Start();
        cl_kernel mk2_kernel = clCreateKernel(mk2_prog, "prod_TT", &err);
        double len_mk2_extract = Timer::Stop(TH_mk2_extract, "TH_mk2_extract");
        CL_CHECK_ERROR(err);

        // Cleanup
        int TH_mk2_cleanup = Timer::Start();
        err = clReleaseKernel(mk2_kernel);
        CL_CHECK_ERROR(err);
        err = clReleaseProgram(mk2_prog);
        CL_CHECK_ERROR(err);
        double len_mk2_cleanup = Timer::Stop(TH_mk2_cleanup, "TH_mk2_cleanup");

        resultDB.AddResult("CreateFromSource","short_kernel", "sec", len_short_create);
        resultDB.AddResult("BuildProgram",    "short_kernel", "sec", len_short_build);
        resultDB.AddResult("ExtractKernel",   "short_kernel", "sec", len_short_extract);
        resultDB.AddResult("Cleanup",         "short_kernel", "sec", len_short_cleanup);

/*
        resultDB.AddResult("CreateFromSource","s2l_kernel", "sec", total_s2l_create);
        resultDB.AddResult("BuildProgram",    "s2l_kernel", "sec", total_s2l_build);
        resultDB.AddResult("ExtractKernel",   "s2l_kernel", "sec", total_s2l_extract);
        resultDB.AddResult("Cleanup",         "s2l_kernel", "sec", total_s2l_cleanup);
*/
        resultDB.AddResult("CreateFromSource","long_kernel", "sec", len_long_create);
        resultDB.AddResult("BuildProgram",    "long_kernel", "sec", len_long_build);
        resultDB.AddResult("ExtractKernel",   "long_kernel", "sec", len_long_extract);
        resultDB.AddResult("Cleanup",         "long_kernel", "sec", len_long_cleanup);

        resultDB.AddResult("CreateFromSource","mk_kernel", "sec", len_mk_create);
        resultDB.AddResult("BuildProgram",    "mk_kernel", "sec", len_mk_build);
        resultDB.AddResult("ExtractKernel_1",   "mk_kernel", "sec", len_mk_extract_1);
        resultDB.AddResult("ExtractKernel_2",   "mk_kernel", "sec", len_mk_extract_2);
        resultDB.AddResult("ExtractKernel_3",   "mk_kernel", "sec", len_mk_extract_3);
        resultDB.AddResult("ExtractKernel_4",   "mk_kernel", "sec", len_mk_extract_4);
        resultDB.AddResult("ExtractKernel_5",   "mk_kernel", "sec", len_mk_extract_5);
        resultDB.AddResult("Cleanup",         "mk_kernel", "sec", len_mk_cleanup);

        resultDB.AddResult("CreateFromSource","mk2_kernel", "sec", len_mk2_create);
        resultDB.AddResult("BuildProgram",    "mk2_kernel", "sec", len_mk2_build);
        resultDB.AddResult("ExtractKernel",   "mk2_kernel", "sec", len_mk2_extract);
        resultDB.AddResult("Cleanup",         "mk2_kernel", "sec", len_mk2_cleanup);
    }
}
